package cn.aresoft.excel.common;

import cn.aresoft.configuration.PoiConfigProperties;
import cn.aresoft.excel.ExcelHandleException;
import cn.aresoft.configuration.ExcelSpringContext;
import cn.aresoft.excel.emuns.ExcelExportType;
import cn.aresoft.excel.entity.ExcelDictModel;
import cn.aresoft.excel.entity.ExcelFieldEntity;
import cn.aresoft.excel.entity.ExcelWrapEntity;
import cn.aresoft.excel.utils.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpHeaders;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * @author yangfeng
 * @description:
 * @date 2024/2/2 11:12
 */
public class ExportExcelBase {

    protected Workbook book;
    protected OutputStream outputStream;
    protected String fileName;
    protected ExcelDictSheetUtil excelDictSheetUtil;
    protected ExcelExportType type;
    private static final ExcelFieldConfigUtil excelFieldConfigUtil = ExcelSpringContext.getBean(ExcelFieldConfigUtil.class);
    private static final ExcelStyleUtil excelStyleUtil = ExcelSpringContext.getBean(ExcelStyleUtil.class);
    private static final Integer HANDLER_BATCH_SIZE = 3000;
    private static volatile ThreadPoolExecutor threadPool = null;


    /**
     * 构造函数（往设置的文件里头写入）
     *
     * @param filePath
     * @param fileName
     * @throws FileNotFoundException
     */
    protected ExportExcelBase(String filePath, String fileName, ExcelExportType type) {
        this(fileName, type);
        try {
            this.outputStream = new FileOutputStream(filePath + File.separator + fileName);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw new ExcelHandleException("导出文件创建失败！");
        }
    }

    /**
     * 构造函数（获取输出文件字节数组）
     *
     * @param fileName
     * @throws
     * @throws Exception
     * @throws FileNotFoundException
     */
    protected ExportExcelBase(String fileName, ExcelExportType type) {

        if (fileName.toLowerCase().endsWith("xls")) {
            book = new HSSFWorkbook();
        } else {
            book = new SXSSFWorkbook();
        }
        if (threadPool == null) {
            synchronized (ThreadPoolExecutor.class) {
                PoiConfigProperties poiConfigProperties = ExcelSpringContext.getBean(PoiConfigProperties.class);
                threadPool = new ThreadPoolExecutor(poiConfigProperties.getThreadCoreSize(), poiConfigProperties.getThreadMaxSize(), 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(poiConfigProperties.getBlockingQueueSize()));
            }
        }
        this.excelDictSheetUtil = new ExcelDictSheetUtil(book, "字典规范");
        this.fileName = fileName;
        this.type = type;
    }

    /**
     * 设置单元格样式
     *
     * @param annotationList
     * @return
     */
    protected int getAllCellStyle(Sheet sheet, String title, List<ExcelFieldEntity> annotationList, Map<String, CellStyle> styleMap) {

        int rowNum = 0;
        if (StringUtils.isNotEmpty(title)) {
            /**设置标题*/
            Cell titleCell = sheet.createRow(0).createCell(0);
            titleCell.setCellValue(title);
            CellRangeAddress region = new CellRangeAddress(0, 0, 0, annotationList.size() - 1);
            sheet.addMergedRegion(region);
            titleCell.setCellStyle(styleMap.get(ExcelStyleUtil.TITLE_STYLE));
            rowNum++;
        }
        //判断是否有分组设置
        ExcelFieldEntity fieldEntity = annotationList.stream().filter(item -> StringUtils.isNotEmpty(item.getGroupName())).findFirst().orElse(null);
        Row groupRow = null;
        if (fieldEntity != null) {
            groupRow = sheet.createRow(rowNum);
            rowNum++;
        }
        //设置表头
        Row row = sheet.createRow(rowNum);
        rowNum++;
        sheet.createFreezePane(0, rowNum, 0, rowNum);
        List<String> hasSetGroupName = new ArrayList<>();
        for (int i = 0; i < annotationList.size(); i++) {
            ExcelFieldEntity excelFieldEntity = annotationList.get(i);
            Cell headCell = null;
            if (groupRow != null) {
                if (StringUtils.isEmpty(excelFieldEntity.getGroupName())) {
                    CellRangeAddress region = new CellRangeAddress(groupRow.getRowNum(), groupRow.getRowNum() + 1, i, i);
                    sheet.addMergedRegion(region);
                    headCell = groupRow.createCell(i);
                } else {
                    if (!hasSetGroupName.contains(excelFieldEntity.getGroupName())) {
                        long rangeCount = annotationList.stream().filter(item -> item.getGroupName().equals(excelFieldEntity.getGroupName())).count();
                        CellRangeAddress region = new CellRangeAddress(groupRow.getRowNum(), groupRow.getRowNum(), i, (int) (i + rangeCount - 1));
                        sheet.addMergedRegion(region);
                        Cell groupRowCell = groupRow.createCell(i);
                        groupRowCell.setCellStyle(styleMap.get(ExcelStyleUtil.TITLE_STYLE));
                        groupRowCell.setCellValue(excelFieldEntity.getGroupName());
                        hasSetGroupName.add(excelFieldEntity.getGroupName());
                    }
                    headCell = row.createCell(i);
                }
            } else {
                headCell = row.createCell(i);
            }
            CellStyle evenDateStyle = book.createCellStyle();
            DataFormat evenformat = book.createDataFormat();
            evenDateStyle.cloneStyleFrom((CellStyle) styleMap.get(ExcelStyleUtil.CELL_STYLE));
            evenDateStyle.setAlignment(HorizontalAlignment.forInt(excelFieldEntity.getAlign()));
            evenDateStyle.setDataFormat(evenformat.getFormat(excelFieldEntity.getPattern()));
            styleMap.put(ExcelStyleUtil.CELL_STYLE + i, evenDateStyle);
            if (Boolean.TRUE.equals(excelFieldEntity.getRequired())) {
                CellStyle style = book.createCellStyle();
                style.cloneStyleFrom(styleMap.get(ExcelStyleUtil.HEAD_STYLE));
                style.setFillForegroundColor(IndexedColors.RED.index);
                style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                headCell.setCellStyle(style);
            } else {
                headCell.setCellStyle(styleMap.get(ExcelStyleUtil.HEAD_STYLE));
            }
            headCell.setCellValue(excelFieldEntity.getTitle());
            int headWidth = headCell.getStringCellValue().getBytes().length * 256 + 512;
            sheet.setColumnWidth(i, headWidth);
            excelDictSheetUtil.validateData(sheet, excelFieldEntity, i, rowNum);
            sheet.setDefaultColumnStyle(i, evenDateStyle);
            sheet.setColumnHidden(i, excelFieldEntity.isCollapsed());
        }
        return rowNum;
    }

    /**
     * 关闭写入文件流(针对于保存到本地文件的方法)
     */
    public void closeFileStream() {
        if (outputStream != null) {
            try {
                book.write(outputStream);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 关闭写入文件流(针对于保存到本地文件的方法)
     */
    public void closeFileStream(String waterMarkStr) {
        if (outputStream != null) {
            try {
                ByteArrayOutputStream byos = new ByteArrayOutputStream();
                book.write(byos);
                byos.flush();
                byos.close();
                byte[] waterMarkBytes = WatermarkUtil.waterMarkGenerator(byos.toByteArray(), waterMarkStr);
                outputStream.write(waterMarkBytes);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 给sheet页设置导出内容
     *
     * @param sheetName  sheet页名称
     * @param title      标题
     * @param clazz      clazz 对象
     * @param resultList 导出数据
     * @param <T>
     * @return
     */
    public <T> void setExcelFileData(String sheetName, String title, Class<? extends Object> clazz, List<T> resultList) {

        try {
            Sheet dataSheet = book.createSheet(sheetName);
            List<ExcelFieldEntity> annotationList = excelFieldConfigUtil.getExportFieldConfig(clazz, type, resultList);
            Map<String, CellStyle> allStyle = excelStyleUtil.createStyles(book);
            int rowNum = getAllCellStyle(dataSheet, title, annotationList, allStyle);
            AtomicInteger atomicInteger = new AtomicInteger(rowNum);
            CompletionService<Integer> completionService = new ExecutorCompletionService<>(threadPool);
            Integer batchMax = resultList.size() / HANDLER_BATCH_SIZE;
            Integer rest = resultList.size() % HANDLER_BATCH_SIZE;
            List<Future<Integer>> futureList = new ArrayList<>();
            for (int i = 0; i < batchMax; i++) {
                List<T> subList = resultList.subList(i * HANDLER_BATCH_SIZE, (1 + i) * HANDLER_BATCH_SIZE);
                futureList.add(completionService.submit(new SheetDataSetThread(subList, dataSheet, annotationList, allStyle, atomicInteger)));
            }
            if (rest > 0) {
                List<T> subList = resultList.subList(batchMax * HANDLER_BATCH_SIZE, batchMax * HANDLER_BATCH_SIZE + rest);
                futureList.add(completionService.submit(new SheetDataSetThread(subList, dataSheet, annotationList, allStyle, atomicInteger)));
            }
            while(!futureList.isEmpty()) {
                Iterator<Future<Integer>> iterator = futureList.iterator();
                while (iterator.hasNext()) {
                    Future<Integer> future = iterator.next();
                    if (future.isDone() && !future.isCancelled()) {
                        Integer size = future.get(); //获取线程执行结果
                        iterator.remove();
                    }
                }
            }
            book.setSheetOrder("字典规范", book.getNumberOfSheets() - 1);
            book.setActiveSheet(0);
        } catch (Exception e) {
            e.printStackTrace();
            this.closeFileStream();
            throw new ExcelHandleException("excel导出失败！");
        }
    }

    class SheetDataSetThread implements Callable<Integer> {

        private List<?> resultList;
        private Sheet dataSheet;
        private List<ExcelFieldEntity> annotationList;
        private Map<String, CellStyle> allStyle;
        private AtomicInteger startRowNum;

        public SheetDataSetThread(List<?> resultList, Sheet dataSheet, List<ExcelFieldEntity> annotationList, Map<String, CellStyle> allStyle, AtomicInteger startRowNum) {
            this.resultList = resultList;
            this.dataSheet = dataSheet;
            this.annotationList = annotationList;
            this.allStyle = allStyle;
            this.startRowNum = startRowNum;
        }

        @Override
        public Integer call() throws Exception {
            return ExcelDataGenerate(resultList, dataSheet, annotationList, allStyle, startRowNum);
        }
    }

    private Integer ExcelDataGenerate(List<?> resultList, Sheet dataSheet,
                                      List<ExcelFieldEntity> annotationList,
                                      Map<String, CellStyle> allStyle, AtomicInteger rowNum) throws Exception {
        Row row;
        for (int j = 0; j < resultList.size(); j++) {
            Object obj = resultList.get(j);
            synchronized (ExportExcelBase.class) {
                row = dataSheet.createRow(rowNum.getAndAdd(1));
            }
            for (int i = 0; i < annotationList.size(); i++) {
                ExcelFieldEntity excelFieldEntity = annotationList.get(i);
                Field field = null;
                Object value = null;
                if (excelFieldEntity.getIsWrapField()) {
                    ExcelWrapEntity excelWrapEntity = excelFieldEntity.getExcelWrapEntity();
                    List wrapList = (List) excelWrapEntity.getWrapListField().get(obj);
                    if (!wrapList.isEmpty()) {
                        for (int k = 0; k < wrapList.size(); k++) {
                            Field subField = excelFieldEntity.getField();
                            if (k == excelWrapEntity.getFieldSeq()) {
                                value = subField.get(wrapList.get(k));
                                field = subField;
                                break;
                            }
                        }
                    }
                } else {
                    field = excelFieldEntity.getField();
                    value = field.get(obj);
                }
                /**如果字段存在存在校验配置，则进行校验*/
                String message = "";
                if (excelFieldEntity.getExcelVaildateEntity() != null) {
                    message = ExcelValidateFieldUtil.validateField(excelFieldEntity, value, obj);
                }
                if (value != null && (field.getType().equals(String.class) || Number.class.isAssignableFrom(field.getType()))) {
                    if (excelFieldEntity.getExcelDictModelList() != null && !excelFieldEntity.getExcelDictModelList().isEmpty()) {
                        String code = String.valueOf(value);
                        ExcelDictModel excelDictModel = excelFieldEntity.getExcelDictModelList().stream().filter(dictModel -> dictModel.getValue().equals(code)).findFirst().orElse(null);
                        if (excelDictModel != null) {
                            value = excelDictModel.getText();
                        }
                    } else if (excelFieldEntity.getSelfValueHandler() != null) {
                        value = excelFieldEntity.getSelfValueHandler().convertValueToText(obj, String.valueOf(value));
                    }
                }
                Cell dataCell = ExcelCellUtil.setCellValue(row, i, value);
                CellStyle cellStyle = allStyle.get(ExcelStyleUtil.CELL_STYLE + i);
                if (cellStyle != null) {
                    if (StringUtils.isEmpty(message)) {
                        dataCell.setCellStyle(cellStyle);
                    } else {
                        CellStyle style = book.createCellStyle();
                        style.cloneStyleFrom(cellStyle);
                        style.setFillForegroundColor(IndexedColors.YELLOW.index);
                        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                        dataCell.setCellStyle(style);
                    }
                }
            }
        }
        return resultList.size();
    }


    /**
     * 下载数据导出给前端
     *
     * @return
     */
    public <T> ResponseEntity<byte[]> excelfileDownload() {
        ResponseEntity<byte[]> responseEntity = null;
        try (ByteArrayOutputStream byos = new ByteArrayOutputStream();) {
            book.write(byos);
            byos.flush();
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", new String(
                    fileName.getBytes(StandardCharsets.UTF_8), "ISO8859-1"));
            responseEntity = new ResponseEntity<>(byos.toByteArray(),
                    headers, HttpStatus.CREATED);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ExcelHandleException("文件下载失败！");
        }
        return responseEntity;
    }

    /**
     * 下载数据导出给前端
     *
     * @return
     */
    public <T> ResponseEntity<byte[]> excelfileDownload(String waterMarkStr) {
        ResponseEntity<byte[]> responseEntity = null;
        try (ByteArrayOutputStream byos = new ByteArrayOutputStream();) {
            book.write(byos);
            byos.flush();
            byte[] waterMarkBytes = WatermarkUtil.waterMarkGenerator(byos.toByteArray(), waterMarkStr);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", new String(
                    fileName.getBytes(StandardCharsets.UTF_8), "ISO8859-1"));
            responseEntity = new ResponseEntity<>(waterMarkBytes,
                    headers, HttpStatus.CREATED);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ExcelHandleException("文件下载失败！");
        }
        return responseEntity;
    }


}
